// Copyright (C) 2022 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

//遇到报错请在当前目录下执行这个命令： go mod download golang.org/x/text
import (
	"bufio"
	"bytes"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/big"
	"mime"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"
)

const HttpPort = 9000

var exPath string

// CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
// CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go
func cors(fs http.Handler, version string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// return if you do not want the FileServer handle a specific request
		r.Header.Add("Cross-Origin-Opener-Policy", "same-origin")
		r.Header.Add("Cross-Origin-Embedder-Policy", "require-corp")
		w.Header().Add("Cross-Origin-Opener-Policy", "same-origin")
		w.Header().Add("Cross-Origin-Embedder-Policy", "require-corp")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Credentials", "true")
		w.Header().Set("Access-Control-Allow-Headers", "x-requested-with, authorization, blade-auth") //*
		w.Header().Set("Access-Control-Allow-Methods", "*")                                           //*
		w.Header().Set("Access-Control-Max-Age", "3600")
		w.Header().Set("data-version", version)
		fs.ServeHTTP(w, r)
	}
}

func exist(path string) bool {
	_, err := os.Stat(path)
	if err != nil {
		if os.IsExist(err) {
			return true
		}
		return false
	}
	return true
}
func genSSL() {
	if exist("cert/keyFile.key") || exist("cert/certFile.pem") {
		fmt.Println("keyFile.key exists")
		return
	}
	max := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, _ := rand.Int(rand.Reader, max)
	subject := pkix.Name{
		Organization:       []string{"www.smartperf.com"},
		OrganizationalUnit: []string{"ITs"},
		CommonName:         "www.smartperf.com",
	}
	certificate509 := x509.Certificate{
		SerialNumber: serialNumber,
		Subject:      subject,
		NotBefore:    time.Now(),
		NotAfter:     time.Now().AddDate(10, 0, 0),
		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		IPAddresses:  []net.IP{net.ParseIP("127.0.0.1")},
	}
	chekDir("cert")
	pk, _ := rsa.GenerateKey(rand.Reader, 1024)
	derBytes, _ := x509.CreateCertificate(rand.Reader, &certificate509, &certificate509, &pk.PublicKey, pk)
	certOut, _ := os.Create("cert/certFile.pem")
	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	certOut.Close()
	keyOut, _ := os.Create("cert/keyFile.key")
	pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(pk)})
	keyOut.Close()
}
func main() {
	checkPort(HttpPort)
	genSSL()
	exPath = getCurrentAbPath()
	fmt.Println(exPath)
	go func() {
		version := ""
		readVersion, versionErr := ioutil.ReadFile(exPath + "/version.txt")
		if versionErr != nil {
			version = ""
		} else {
			version = string(readVersion)
		}
		mux := http.NewServeMux()
		mime.TypeByExtension(".js")
		mime.AddExtensionType(".js", "application/javascript")
		log.Println(mime.TypeByExtension(".js"))
		mux.HandleFunc("/upload", uploadHandler)
		mux.HandleFunc("/logger", consoleHandler)
		mux.Handle("/upload/", http.StripPrefix("/upload/", http.FileServer(http.Dir(exPath+"/upload"))))
		fs := http.FileServer(http.Dir(exPath + "/"))
		mux.Handle("/application/", http.StripPrefix("/application/", cors(fs, version)))
		ser := &http.Server{
			Addr:    fmt.Sprintf(":%d", HttpPort),
			Handler: mux,
		}
		log.Println(fmt.Sprintf("HTTP[%d]服务启动", HttpPort))
		open(fmt.Sprintf("https://localhost:%d/application", HttpPort))
		err := ser.ListenAndServeTLS("cert/certFile.pem", "cert/keyFile.key")
		CheckErr(err)
	}()
	select {}
}

func getPidByPort(portNumber int) int {
	resPid := -1
	var out bytes.Buffer
	cmdRes := exec.Command("cmd", "/c", fmt.Sprintf("netstat -ano -p tcp | findstr %d", portNumber))
	cmdRes.Stdout = &out
	cmdRes.Run()
	cmdResStr := out.String()
	findStr := regexp.MustCompile(`\s\d+\s`).FindAllString(cmdResStr, -1)
	if len(findStr) > 0 {
		pid, err := strconv.Atoi(strings.TrimSpace(findStr[0]))
		if err != nil {
			resPid = -1
		} else {
			resPid = pid
		}
	}
	return resPid
}

type LoggerReq struct {
	FileName string `json:"fileName"`
	FileSize string `json:"fileSize"`
}

func consoleHandler(w http.ResponseWriter, r *http.Request) {
	chekDir(exPath + "/logger")
	var now = time.Now()
	var fileName = fmt.Sprintf("%d-%d-%d", now.Year(), now.Month(), now.Day())
	dst, err := os.OpenFile(exPath+"/logger/"+fileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND|os.O_SYNC, 0666)
	CheckErr(err)
	contentType := r.Header["Content-Type"]
	if len(contentType) > 0 {
		contentTypeName := contentType[0]
		if strings.HasPrefix(contentTypeName, "application/json") {
			decoder := json.NewDecoder(r.Body)
			var req LoggerReq
			decoder.Decode(&req)
			dst.WriteString(fmt.Sprintf("%s %s (%s M)\n", now.Format("2006-01-02 15:04:05"), req.FileName, req.FileSize))
			fmt.Fprintf(w, fmt.Sprintf("日志写入成功%s", exPath))
		}
	}
}
func uploadHandler(w http.ResponseWriter, r *http.Request) {
	defer func() {
		var err = recover()
		fmt.Println(err)
	}()
	chekDir(exPath + "/upload")
	contentType := r.Header["Content-Type"]
	if len(contentType) > 0 {
		contentTypeName := contentType[0]
		if strings.HasPrefix(contentTypeName, "multipart/form-data") {
			err := r.ParseMultipartForm(32 << 20)
			CheckErr(err)
			file, header, err := r.FormFile("file")
			CheckErr(err)
			filename := header.Filename
			index := strings.LastIndex(filename, ".")
			distFileName := fmt.Sprintf("%d", time.Now().Unix())
			distFileSuffix := filename[index:]
			path := fmt.Sprintf("/upload/%s%s", distFileName, distFileSuffix)
			dst, err := os.OpenFile(exPath+path, os.O_WRONLY|os.O_CREATE, 0666)
			CheckErr(err)
			defer dst.Close()
			if _, err := io.Copy(dst, file); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			databaseUrl := transformDatabase(distFileName, distFileSuffix)
			if databaseUrl != "" {
				ohosTsPath := fmt.Sprintf("./upload/%s.ohos.ts", databaseUrl)
				result, _ := PathExists(ohosTsPath)
				if result {
					readFile, readErr := ioutil.ReadFile(ohosTsPath)
					if readErr == nil {
						fmt.Println(string(readFile))
						split := SplitLines(string(readFile))
						fmt.Println(split)
						if len(split) > 1 {
							if strings.HasSuffix(split[0], ":0") {
								fmt.Fprintf(w, fmt.Sprintf("/upload/%s", databaseUrl))
								return
							}
						}
					}
				}
			}
			http.Error(w, "文件生成失败", http.StatusNotFound)
			return
		}
	}
}
func SplitLines(s string) []string {
	var lines []string
	sc := bufio.NewScanner(strings.NewReader(s))
	for sc.Scan() {
		lines = append(lines, sc.Text())
	}
	return lines
}

func readFileFirstLine(path string) string {
	file, err := os.Open(path)
	if err != nil {
		return ""
	}
	defer file.Close()

	readFile := bufio.NewReader(file)
	line, readErr := readFile.ReadString('\n')
	if readErr != nil || io.EOF == err {
		return ""
	}
	return line
}

func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func chekDir(path string) {
	_, err := os.Stat(path)
	if err != nil {
		err := os.Mkdir(path, os.ModePerm)
		if err != nil {
			fmt.Printf("mkdir failed![%v]\n", err)
		} else {
			fmt.Printf("mkdir success!\n")
		}
	}
}
func CheckErr(err error) {
	if err != nil {
		log.Panicln(err)
	}
}

func open(url string) error {
	if isWindows() {
		return openUrlWindows(url)
	} else if isDarwin() {
		return openUrlDarwin(url)
	} else {
		return openUrlOther(url)
	}
}

func openUrlWindows(url string) error {
	cmd := "cmd"
	args := []string{"/c", "start", url}
	return exec.Command(cmd, args...).Start()
}
func openUrlDarwin(url string) error {
	var cmd = "open"
	var args = []string{url}
	return exec.Command(cmd, args...).Start()
}
func openUrlOther(url string) error {
	var cmd = "xdg-open"
	var args = []string{url}
	return exec.Command(cmd, args...).Start()
}

func isWindows() bool {
	return runtime.GOOS == "windows"
}
func isDarwin() bool {
	return runtime.GOOS == "darwin"
}

func transformDatabase(name string, suffix string) string {
	if isWindows() {
		cmd := exec.Command(
			"cmd",
			"/c",
			`.\bin\trace_streamer_windows.exe`,
			fmt.Sprintf(`.\upload\%s%s`, name, suffix),
			"-e",
			fmt.Sprintf(`.\upload\%s.db`, name),
		)
		var stdout, stderr bytes.Buffer
		cmd.Stdout = &stdout // 标准输出
		cmd.Stderr = &stderr // 标准错误
		err := cmd.Run()
		if err != nil {
			return ""
		}
		return fmt.Sprintf("%s.db", name)
	} else if isDarwin() {
		cmd := exec.Command(
			"/bin/bash",
			"-c",
			fmt.Sprintf("%s/bin/trace_streamer_mac %s/upload/%s%s -e %s/upload/%s.db", exPath, exPath, name, suffix, exPath, name),
		)
		var stdout, stderr bytes.Buffer
		cmd.Stdout = &stdout // 标准输出
		cmd.Stderr = &stderr // 标准错误
		err := cmd.Run()
		outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
		fmt.Printf("out:\n%s\n :\n%s\n", outStr, errStr)
		if err != nil {
			return ""
		}
		return fmt.Sprintf("%s.db", name)
	} else {
		cmd := exec.Command(
			"/bin/bash",
			"-c",
			fmt.Sprintf("%s/bin/trace_streamer_linux %s/upload/%s%s -e %s/upload/%s.db", exPath, exPath, name, suffix, exPath, name),
		)
		var stdout, stderr bytes.Buffer
		cmd.Stdout = &stdout // 标准输出
		cmd.Stderr = &stderr // 标准错误
		err := cmd.Run()
		outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
		fmt.Printf("out:\n%s\n :\n%s\n", outStr, errStr)
		if err != nil {
			return ""
		}
		return fmt.Sprintf("%s.db", name)
	}
	return ""
}
func getCurrentAbPath() string {
	dir := getExecutePath()
	tmpDir, _ := filepath.EvalSymlinks(os.TempDir())
	if strings.Contains(dir, tmpDir) {
		return getCallerPath()
	}
	return dir
}

func getCallerPath() string {
	var pth string
	_, fName, _, ok := runtime.Caller(0)
	if ok {
		pth = path.Dir(fName)
	}
	return pth
}
func getExecutePath() string {
	pth, err := os.Executable()
	if err != nil {
		log.Fatal(err)
	}
	res, _ := filepath.EvalSymlinks(filepath.Dir(pth))
	return res
}

func checkPort(port int) {
	if isWindows() {
		pid := getPidByPort(port)
		if pid != -1 {
			res := exec.Command("cmd", "/c", fmt.Sprintf("taskkill /F /PID %d /T", pid))
			res.Run()
		}
	}
}